<?php
/**
 * XmuSlh, web platform of Student Association Federation of Xiamen University
 * 
 * Rewritting one or some pages of StatusNet. 
 * To get the original version, please visit <http://status.net/>.
 * 
 * StatusNet, the distributed open-source microblogging tool
 * 
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Make the every action a html page.
 * The reason why to do such design is that every action from the browser
 * may be mapped by a specific page, so just do it!
 * It Action class is the based class of all the actions!
 * 
 * 所有___Action类的父类, 定义了基本的HTML渲染和输出方法, 同时还有缓存, 错误处理等过程. 
 * 
 * @author		ytukie <ytukie@gmail.com>
 * @version		0.42
 * 
 * 0.2)  Adds some jQuery features..
 * 0.21) Fix the 'clientError' and 'serverError' function.
 * 0.3)  Confirms a first version of the layout, so adjust some functions.
 * 		 a. Adjusting.
 * 		 b. Adds the showInnerContent() and showInnerAside() show blocks.
 * 0.31) Adds 'unsetSessionToken' funciton.
 * 0.32) Adds 'pagination' function.
 * 0.33) Adds 'showPrimayNav' block.
 * 0.34) Moves the 'jquery' lib in the <head>
 * 0.35) Adds the 'AdminAsidenav' in the AsideBlock according to the logged in status.
 * 0.36) Adds the 'showLoginNav' block.
 * 0.36) Adds the 'showSecondaryNav' block.
 * 0.4)  Fixes the 'todo's.
 * 0.41) Adds some features on the pagination.
 * 0.42) Adds the js code to judge the IE7.
 * 0.43) Adds the ads of the ChinaNet using ylFloat.
 * 0.44) Adds the vedio of Fancybox with both css and js.
 */

// XmuSlh only.
if ( !defined('XMUSLH')) { exit(1); }

// Requirements!
require_once INSTALLDIR . '/lib/slh_htmloutputter.php';
require_once INSTALLDIR . '/lib/slh_util.php';
require_once INSTALLDIR . '/lib/slh_rights.php';
require_once INSTALLDIR . '/lib/slh_clientexception.php';
require_once INSTALLDIR . '/lib/slh_serverexception.php';
require_once INSTALLDIR . '/lib/slh_weibo.php';

require_once INSTALLDIR . '/widgets/slh_topnav_guide.php';
require_once INSTALLDIR . '/widgets/slh_asidenav_admin.php';
require_once INSTALLDIR . '/widgets/slh_topnav_login.php';
require_once INSTALLDIR . '/widgets/slh_bottomnav_guide.php';


/**
 * Base class for all actions.
 * 
 * This is the base class for all actions in the package. An action is
 * more or less a "view" in an MVC framework.
 * Actions are responsible for extracting and validating parameters;
 * using model classes to read and write to the database; and doing output.
 */
class Action extends HTMLOutputter
{
	// Arguments.
	var $args;
	
	/**
	 * Constructor
	 * 
	 * None.
	 */
	function __construct($output = 'php://output', 
						 $indent = 'true')
	{
		parent::__construct($output, $indent);
	}
	
//////// Procedure relative /////////////////////////////////////////////////////////
	/**
	 * Has etag? (private)
	 */
	function _hasEtag($etag, $if_none_match)
	{
		$etag = explode(',', $if_none_match);
		return in_array($etag, $etags) || in_array('*', $etags);
	}
		
	/**
	 * Return last modified, if applicable, overloading.
	 */
	function lastModified()
	{
		// For comparison with If-Last-Modified
		// If not applicable, return null.
		return null;
	}
	
	/**
	 * Return etag, if applicable, overloading.
	 */
	function etag()
	{
		return null;
	}
	
	/**
	 * Return ture if read only, overloading.
	 */
	function isReadOnly($args)
	{
		return false;
	}
	
	/**
	 * Is this action cacheable?
	 * 
	 * If the action returns a last-modified
	 */
	function isCacheable()
	{
		return true;
	}
	
	/**
	 * check if the current request is a POST.
	 */
	function isPost()
	{
		return ($_SERVER['REQUEST_METHOD' == 'POST']);
	}
	
	/**
	 * Returns query argument or default value if not found.
	 */
	function getArg($key, 
					$def = null)
	{
		if (array_key_exists($key, $this->args)) {
			return $this->args[$key];
		} else {
			return $def;
		}
	}
	
	/**
	 * Returns trimmed query argument or default value if not found.
	 */
	function getTrimmedArg($key, 
						   $def = null)
	{
		$arg = $this->getArg($key, $def);
		return is_string($arg) ? trim($arg) : $arg;	
	}
	
	/**
	 * Returns arguments sfficient for re-constructing URL
	 */
	function returnToArgs()
	{
		$action = $this->getTrimmedArg('action');
		$args	= $this->args;
		unset($args['action']);
		
		if (array_key_exists('sumbit', $args)) {
			unset($args['submit']);
		}
		foreach (array_keys($_COOKIE) as $cookie) {
			unset($args[$cookie]);
		}
		
		return array($action, $args);
	}
	
	/**
	 * Returns the current URL.
	 */
	function selfUrl()
	{
		list($action, $args) = $this->returnToArgs();
		return slh_action2url($action, $args);
	}
	
	/**
	 * For initializing members of the class.
	 * And do some preparation like copying the args.
	 * 
	 * FIXME: Just use the reference.
	 * 
	 * @return boolean true
	 */
	function prepare(&$argarray)
	{
		$this->args =& $argarray;
		
		return true;
	}
	
	/**
	 * Handler method
	 */
	function handle($argarray = null)
	{
		header('Vary: Accept-Encoding,Cookie');
		
		$lm		= $this->lastModified();
		$etag	= $this->etag();
		
		if ($etag) {
			header('ETag: ' . $etag);
		}
		
		if ($lm) {
			header('Last-Modified: ' . date(DATE_RFC1123, $lm));
			if ($this->isCacheable()) {
				header('Expires: ' . gmdate('D, d M Y H:i:s', 0) . ' GMT');
				header('Cache-Contorl: private, must-revalidate, max-age=0');
				header('Pragma:');
			}
		}
		
		$checked = false;
		if ($etag) {
			$if_none_match = (array_key_exists('HTTP_IF_NONE_MATCH', $_SERVER)) ?
				$_SERVER['HTTP_IF_NONE_MATCH'] : null;
			
			if ($if_none_match) {
				// If this check fails, ignore the if-modified-since below.
				$checked = true;
				if ($this->_hasEtag($etag, $if_none_match)) {
					header('HTTP/1.1 304 Not Modified');
// FIXME: Find a better way to cache!
					exit(0);
				}
			}
		}
		
		if ( !$checked && $lm && array_key_exists('HTTP_IF_MODIFIED_SINCE', $_SERVER)) {
			$if_modified_since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
			$ims = strtotime($if_modified_since);
			if ($lm < $ims) {
				header('HTTP/1.1 304 Not Modified');
// FIXME: Also find a better way to cache!
				exit(0);
			}
		}
	}
	
	/** 
	 * Check the session token.
	 * 
	 * Checks that the current form has the correct session token,
	 * and throw an exception if it does not.
	 */
	function checkSessionToken()
	{
		$token = $this->getTrimmedArg('token');
		if (empty($token) || $token != slh_session_token()) {
			$this->clientError('There was something wrong with your session token.');
		}
	}
	
	/**
	 * Unset the session token.
	 * 
	 * After unsetting the sessin token. Refreshing would not lead to a new
	 * data post, but a client error or some other handler beacause of failed
	 * in checkSessionToken().
	 */
	function unsetSessionToken()
	{
		if (isset($_SESSION['token'])) {
			unset($_SESSION['token']);
		}
	}
/////////////////////////////////////////////////////////////////////////////////////

//////// Sections relative //////////////////////////////////////////////////////////
	/**
	 * Show header of the page.
	 */
	function showHeader()
	{
		$this->elementStart('div', array('id' => 'header'));
		
		$this->showLogo();
		$this->showLoginNav();
		$this->showPrimaryNav();
		
		$this->elementEnd('div');	
	}
	
	/**
	 * Show Footer.
	 */
	function showFooter()
	{
		$this->elementStart('div', array('id' => 'footer'));
		
		$this->showSecondaryNav();
		// $this->showLicenses();
		
		$this->elementEnd('div');
	}
	
	/**
	 * Show core.
	 */
	function showCore()
	{
		$this->elementStart('div', array('id' => 'core'));
		
		// Not actually needed currently.
		// $this->showLocalNavBlock();
		$this->showContentBlock();
		$this->showAsideBlock();
		
		$this->elementEnd('div');
	}
	
	/**
	 * Show themed shortcut icon
	 */
	function showShortcutIcon()
	{
		$path = slh_full_path('theme/base/common/favicon.ico');
        $this->element('link', array('rel' => 'shortcut icon',
                                     'href' => $path));
	}
	
	/**
	 * Show stylesheets.
	 * Includes the style sheets!
	 */
	function showStylesheets()
	{
		$this->primaryCssLink(null, 'screen, projection, tv, print');
	}
	
	/**
	 * Add the CSS into the display!
	 */
	function primaryCssLink($mainTheme = null, 
							$media = null)
	{
		$this->cssLink('theme/base/css/xmuslh.base.css', $mainTheme, $media);
	}
	
	/**
	 * Adds the JQuery libaray in the head.
	 */
	function showBaseScripts()
	{
		$this->script('jquery.min.js');
	}
	
	/**
	 * Show others JavaScript lib!
	 */
	function showScripts()
	{
		$topbarurl = slh_full_path('js/topbar/jquery.topbar.min.js');
		$gourl = slh_full_path('js/topbar/topbar.go.js');
		
		/*Warning the IE7 and lower.*/
		$this->raw('<!--[if lte IE 7]>' .
				   '<script type="text/javascript" src='.$topbarurl.'></script>' .
				   '<script type="text/javascript" src='.$gourl.'></script>' .
				   '<![endif]-->');
		
		$this->script('ylfloat/YlFloat.js');
		
		$this->cssLink('js/fancybox/jquery.fancybox-1.3.4.css', null, null);
		$this->script('fancybox/jquery.fancybox-1.3.4.pack.js');
		$this->script('fancybox/fancybox.go.js');
	}
	
/////////////////////////////////////////////////////////////////////////////////////

//////// Tags relattive /////////////////////////////////////////////////////////////
	
	/**
	 * Show page, a templated method.
	 */
	function showPage()
	{
		$this->startHTML();
		$this->showHead();
		$this->showBody();
		$this->endHTML();
	}
	
	/**
	 * Ends the HTML tags.
	 * 
	 * Do some performace detection.
	 */
	function endHTML()
	{
		global $_startTime;

        if (isset($_startTime)) {
            $endTime = microtime(true);
            $diff = round(($endTime - $_startTime) * 1000);
            $this->raw('<!-- '. $diff .'ms -->');
        }
        
		return parent::endHTML();
	}
	
	/**
	 * Show <head> element, a templated method.
	 */
	function showHead()
	{
		$this->elementStart('head');

		$this->showTitle();
		$this->showShortcutIcon();
		$this->showStylesheets();
		$this->showBaseScripts();
		$this->extraHead();
		
		$this->elementEnd('head');
	}
	
	/**
	 * Show extra stuff in <head>, overload later?
	 */
	function extraHead()
	{
		;
	}
	
	/**
	 * Show Body! The templated design pattern.
	 */
	function showBody()
	{
		$this->elementStart('body');
		
		$this->elementStart('div', array('id' => 'wrap'));
		
		$this->showHeader();
		$this->showCore();
		$this->showFooter();
		
		$this->elementEnd('div');
		$this->showScripts();
		
		$this->elementEnd('body');
	}
	
	/**
	 * Show <title> element.
	 */
	function showTitle()
	{
		$this->element('title', 
					   null,
					   $this->title() . ' - 厦门大学学生社团联合会™');
	}
	
	/**
	 * Returns the page title. Overload later!
	 */
	function title()
	{
		return '木有标题';
	}
	
/////////////////////////////////////////////////////////////////////////////////////

//////// Main sections //////////////////////////////////////////////////////////////
	/**
	 * Show content block.
	 * showCore() {}
	 */
	function showContentBlock()
	{
		$this->elementStart('div', array('id' => 'contentblock'));
		
		$this->showInnerContent();
		
		$this->elementEnd('div');
	}
	
	/**
	 * Show the inner div in the content block.
	 * showContentBlock() {}
	 * 
	 * Should be overrided.
	 */
	function showInnerContent()
	{
	}
	
	/**
	 * Show aside.
	 * showCore() {}
	 */
	function showAsideBlock()
	{
		$this->elementStart('div', array('id' => 'asideblock'));
		
		$this->showInnerAside();
		
		$this->elementEnd('div');
	}
	
	/**
	 * Show the inner div in the aside block.
	 * showAsideBlock() {}
	 * 
	 * Should be overrided?
	 * 
	 * Add the AdminAsidenav()?
	 * 
	 * FIXME: For $association or $profile in the session.
	 */
	function showInnerAside()
	{
		if (slh_logged_in()) {
			$nav = new AdminAsidenav($this, $this->args);
			$nav->show();
		} else {
			// Check if MOBILE;
			$agent = $_SERVER['HTTP_USER_AGENT'];
			if (strpos($agent,"iPhone") || 
			    strpos($agent,"UCWEB") || 
			    strpos($agent,"Android") || 
			    strpos($agent,"Windows CE") || 
			    strpos($agent,"MIDP-2.0") || 
			    strpos($agent,"Opera Mini") || 
			    strpos($agent,"SymbianOS")) {
			   	// Hide if mobile.
				return;
			}
			
			$weibo = new Weibo($this, 
							   slh_config('weibo', 'default_weibo_id'), 
							   '社联会');
			$weibo->show();
		}
		
	}
	
	/**
	 * Show the primary navigation columns at the top.
	 */
	function showPrimaryNav()
	{
		$nav = new GuideTopnav($this, $this->args);
		$nav->show();
	}
	
	/**
	 * Show the secondary navigation columns at the bottom.
	 */
	function showSecondaryNav()
	{
		$nav = new GuideBottomnav($this);
		$nav->show();
	}
	
	/**
	 * Show the login navigation block.
	 */
	function showLoginNav()
	{
		$nav = new LoginTopnav($this, $this->args);
		$nav->show();
	}
	
	/**
	 * Show the Logo!
	 */
	function showLogo()
	{
		$this->elementStart('address', array('id' => 'logo'));
		$this->elementStart('a', array('href' => slh_action2url('publicnews'),
									   'target' => '_self'));
		$this->element('img', array('src' => slh_full_path('theme/base/common/logo.png'),
		                                     'alt' => slh_config('site', 'name')));
		$this->element('span', 'csshide', slh_config('site', 'name'));
		$this->elementEnd('a');
		$this->elementEnd('address');
	}

/////////////////////////////////////////////////////////////////////////////////////

//////// Others /////////////////////////////////////////////////////////////////////
	/**
	 * Server error!
	 */
	function serverError($msg, 
						 $code = 500)
	{
		$action = $this->getTrimmedArg('action');
		slh_debug("Server error '$code' on '$action': $msg", __FILE__);
throw new ServerException($msg, $code);
	}
	
	/**
	 * Client error!
	 */
	function clientError($msg, 
						 $code = 400)
	{
		$action = $this->getTrimmedArg('action');
		slh_debug("User error '$code' on '$action': $msg", __FILE__);
throw new ClientException($msg, 404);
	}
	
/////////////////////////////////////////////////////////////////////////////////////

	/**
	 * Generate pagination links.
	 * 
	 * @param $have_before, boolean, is there something before?
	 * @param $have_after, boolean, is there something after?
	 * @param $page, integer, current page.
	 * @param $action, string, current action.
	 * @param $args, array, rest of query arguments.
	 */
	function pagination($have_before,
						$have_after,
						$page,
						$action,
						$args = null)
	{
		// Does a littel before-after block for next/prev page.
		if ($have_before || $have_after) {
			$this->elementStart('dl', array('id' => 'pagination'));
			$this->element('dt', null, '分页栏');
			$this->elementStart('dd', null);
			$this->elementStart('ul');
		
			if ($have_before) {
				$pargs = array('page' => $page - 1);
				$this->elementStart('li', 'nav1');
				$this->element('a', array('href' => slh_action2url($action, $args, $pargs),
										  'title' => '浏览前一页'), '之前');
				$this->elementEnd('li');
				$this->elementStart('li', 'nav2');
				$this->element('a', array('href' => slh_action2url($action, $args, $pargs),
										  'title' => '浏览前一页'), '之前');
				$this->elementEnd('li');
			} else {
				$this->element('li', array('class' => 'nav1'));
				$this->element('li', array('class' => 'nav2'));
			}
			// The middle one.
			$this->elementStart('li', 'nav3');
			$this->element('a', array('href' => '#',
									  'title' => '返回顶部'), '当前');
			$this->elementEnd('li');
	
			if ($have_after) {
				$pargs = array('page' => $page + 1);
				$this->elementStart('li', 'nav4');
				$this->element('a', array('href' => slh_action2url($action, $args, $pargs),
										  'title' => '浏览下一页'), '之后');
				$this->elementEnd('li');
				$pargs = array('page' => $page + 1);
				$this->elementStart('li', 'nav5');
				$this->element('a', array('href' => slh_action2url($action, $args, $pargs),
										  'title' => '浏览下一页'), '之后');
				$this->elementEnd('li');
			} else {
				$this->element('li', array('class' => 'nav4'));
				$this->element('li', array('class' => 'nav5'));
			}
			
			if ($have_before || $have_after) {
	            $this->elementEnd('ul');
	            $this->elementEnd('dd');
	            $this->elementEnd('dl');
	        }
		}
	}	
}